﻿using System;
using System.Text;
using Nexia.FluentHtmlElements.Html;
using Nexia.FluentHtmlElements.Utilities;

namespace Nexia.FluentHtmlElements.Elements
{
	/// <summary>
	/// The base class for all elements in the FluentHtmlElements library.
	/// </summary>
	/// <typeparam name="T">The type of the return value of chaining methods.</typeparam>
	public abstract class ElementBase<T> where T : ElementBase<T>
	{
		protected ElementBase(string tag) : this(TagBuilderFactory.Create(tag))
		{
		}

		protected ElementBase(ITagBuilder builder)
		{
			Builder = builder;
		}

		/// <summary>
		/// TagBuilder object used to generate HTML for elements.
		/// </summary>
		public ITagBuilder Builder { get; set; }

		/// <summary>
		/// Determines how the HTML element is closed.
		/// </summary>
		protected internal virtual TagRenderMode TagRenderMode
		{
			get { return TagRenderMode.SelfClosing; }
		}

		public override string ToString()
		{
			PreRender();
			return Builder.ToString(TagRenderMode);
		}

		protected internal virtual void PreRender()
		{
		}

		#region Attribute Related

		/// <summary>
		/// Set the 'lang' related attributes.
		/// </summary>
		/// <param name="lang">The language code to set as the value of the language attribute</param>
		public virtual T Lang(string lang)
		{
			SetAttr(HtmlTagAttributes.Lang, lang);
			return setAttrHelper(HtmlTagAttributes.XmlLang, lang);
		}

		/// <summary>
		/// Set the 'title' attribute.
		/// </summary>
		/// <param name="value">The value of the 'title' attribute.</param>
		public virtual T Title(string value)
		{
			return setAttrHelper(HtmlTagAttributes.Title, value);
		}

		/// <summary>
		/// Set the 'id' attribute.
		/// </summary>
		/// <param name="value">The value of the 'id' attribute.</param>
		public virtual T Id(string value)
		{
			return setAttrHelper(HtmlTagAttributes.Id, value);
		}

		/// <summary>
		/// Add a value to the 'rel' attribute.
		/// </summary>
		/// <param name="rel">The value of the rel attribute</param>
		public virtual T Rel(string rel)
		{
			return setAttrHelper(HtmlTagAttributes.Rel, rel);
		}

		/// <summary>
		/// Add a value to the 'tabindex' attribute.
		/// </summary>
		/// <param name="value">The value of the tabindex attribute</param>
		public virtual T TabIndex(short value)
		{
			return setAttrHelper(HtmlTagAttributes.TabIndex, value, x => x > -1);
		}

		/// <summary>
		/// Add a value to the 'class' attribute.
		/// </summary>
		/// <param name="classToAdd">The value of the class to add.</param>
		public virtual T Class(string classToAdd)
		{
			return Class(classToAdd, () => true);
		}

		/// <summary>
		/// Add a value to the 'class' attribute.
		/// </summary>
		/// <param name="classToAdd">The value of the class to add.</param>
		/// <param name="onlyWhen">The class will only be added if this function returns true</param>
		public virtual T Class(string classToAdd, Func<bool> onlyWhen)
		{
			if (!String.IsNullOrEmpty(classToAdd) && onlyWhen())
				Builder.AddCssClass(classToAdd);

			return (T) this;
		}

		/// <summary>
		/// Set the 'style' attribute.
		/// </summary>
		/// <param name="values">A list of funcs, each epxressing a style name value pair.  Replace dashes with 
		/// underscores in style names. For example 'margin-top:10px;' is expressed as 'margin_top => "10px"'.</param>
		public virtual T Styles(params Func<string, string>[] values)
		{
			if (values != null && values.Length > 0)
			{
				var sb = new StringBuilder();
				foreach (var func in values)
				{
					if (func == null)
						continue;

					var value = func(null);

					if (string.IsNullOrEmpty(value))
						continue;

					sb.AppendFormat("{0}:{1};", func.Method.GetParameters()[0].Name.Replace('_', '-'), value);
				}

				if(sb.Length > 0)
					Builder.MergeAttribute(HtmlTagAttributes.Style, sb.ToString());
			}
			return (T) this;
		}

		/// <summary>
		/// Set the 'onclick' attribute.
		/// </summary>
		/// <param name="value">The value for the attribute.</param>
		/// <returns></returns>
		public virtual T OnClick(string value)
		{
			return setAttrHelper(HtmlTagAttributes.OnClick, value);
		}

		/// <summary>
		/// Set the value of the specified attribute.
		/// </summary>
		/// <param name="name">The name of the attribute.</param>
		/// <param name="value">The value of the attribute.</param>
		/// <exception cref="ArgumentException">Thrown when name parameter is null or string.empty</exception>
		public virtual void SetAttr(string name, object value)
		{
			if (String.IsNullOrEmpty(name))
				throw new ArgumentException("Can't be null or empty", "name");

			if (value == null)
				return;

			var valueStr = value.ToString();

			if (String.IsNullOrEmpty(valueStr))
				return;

			Builder.MergeAttribute(name, valueStr, true);
		}

		/// <summary>
		/// Get the value of the specified attribute.
		/// </summary>
		/// <param name="name">The name of the attribute.</param>
		public virtual string GetAttr(string name)
		{
			string result;
			return Builder.Attributes.TryGetValue(name, out result) ? result : String.Empty;
		}

		/// <summary>
		/// Set the value of a specified attribute.
		/// </summary>
		/// <param name="name">The name of the attribute.</param>
		/// <param name="value">The value of the attribute.</param>
		public virtual T Attr(string name, object value)
		{
			return setAttrHelper(name, value);
		}

		#endregion Attribute Related


		private T setAttrHelper<TValueType>(string key, TValueType value)
		{
			return setAttrHelper(key, value, x => true);
		}

		private T setAttrHelper<TValueType>(string key, TValueType value, Func<TValueType, bool> onlyWhen)
		{
			if (onlyWhen(value))
				SetAttr(key, value);
			return (T) this;
		}
	}
}